﻿namespace EasyToolkit.Excel.BindingTemplates.Definitions
{
    using System;
    using System.Linq;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using EasyToolkit.BindingTemplates.Definitions;
    using EasyToolkit.BindingTemplates.Definitions.Binding;
    using EasyToolkit.Excel.ContextualMenus;
    using EasyToolkit.Tools.Extensions;
    using EasyToolkit.Tools.Reflection;
    using Microsoft.Office.Interop.Excel;

    class ExcelTemplateDefinition : ExcelTemplateDefinitionPart
    {
        #region const
        public const string CONTEXTUALMENU_START_FORMAT = "<ContextualMenu*Name='{0}'";
        public const string TEMPLATE_START_FORMAT = "<Template*Name='{0}'";
        public const string TEMPLATE_END_FORMAT = "<EndTemplate*Name='{0}'";

        public const string TEMPLATE_START_HEADER_ONELINE = "<Header/>";
        public const string TEMPLATE_START_HEADER = "<Header>";
        public const string TEMPLATE_END_HEADER = "</Header>";

        public const string TEMPLATE_START_FOOTER_ONELINE = "<Footer/>";
        public const string TEMPLATE_START_FOOTER = "<Footer>";
        public const string TEMPLATE_END_FOOTER = "</Footer>";
        #endregion

        #region .ctors
        public ExcelTemplateDefinition(string name, Range firstRange, Range lastRange, TemplateOption templateOption)
                                      : base(name, templateOption)
        {
            RetrieveSelectionChangeMethod();
            RetrieveContextualMenuMethod(firstRange);
            ParseTemplate(firstRange, lastRange);
        }
        #endregion

        #region private methods
        /// <summary> Parse the template. Retrieve its components. </summary>
        private void ParseTemplate(Range firstRange, Range lastRange)
        {
            Worksheet worksheet = null;
            try
            {
                int headerSize;
                int footerSize;
                RetrieveHeaderAndFooter(firstRange, lastRange, out headerSize, out footerSize);

                worksheet = firstRange.Worksheet;
                firstRange = worksheet.Cells[firstRange.Row + 1, firstRange.Column + 1];
                lastRange = worksheet.Cells[lastRange.Row, lastRange.Column - 1];

                int width = lastRange.Column - firstRange.Column + 1;
                int height = lastRange.Row - firstRange.Row + 1;

                // Header
                /////////
                if (headerSize != 0)
                {
                    Range headerLastRange;
                    if (Orientation == EasyToolkit.BindingTemplates.Definitions.Orientation.Horizontal)
                        headerLastRange = worksheet.Cells[firstRange.Row + height - 1, firstRange.Column + headerSize - 1];
                    else
                        headerLastRange = worksheet.Cells[firstRange.Row + headerSize - 1, firstRange.Column + width - 1];
                    string name = string.Format("{0}-{1}", Name, "Header");
                    Header = new ExcelHeaderOrFooterTemplateDefinition(true, name, this, firstRange, headerLastRange, TemplateOption);

                    headerLastRange = null;
                }

                // Footer
                /////////
                if (footerSize != 0)
                {
                    Range footerFirstRange;
                    if (Orientation == EasyToolkit.BindingTemplates.Definitions.Orientation.Horizontal)
                        footerFirstRange = worksheet.Cells[lastRange.Row - height - 1, lastRange.Column - footerSize - 1];
                    else
                        footerFirstRange = worksheet.Cells[lastRange.Row - footerSize - 1, lastRange.Column - width - 1];
                    string name = string.Format("{0}-{1}", Name, "Footer");
                    Footer = new ExcelHeaderOrFooterTemplateDefinition(true, name, this, footerFirstRange, lastRange, TemplateOption);

                    footerFirstRange = null;
                }

                // Body
                ///////
                Range realFirstRange;
                Range realLastRange;
                if (Orientation == EasyToolkit.BindingTemplates.Definitions.Orientation.Horizontal)
                {
                    realFirstRange = worksheet.Cells[firstRange.Row, firstRange.Column + headerSize];
                    realLastRange = worksheet.Cells[lastRange.Row, lastRange.Column - footerSize];
                }
                else
                {
                    realFirstRange = worksheet.Cells[firstRange.Row + headerSize, firstRange.Column];
                    realLastRange = worksheet.Cells[lastRange.Row - footerSize, lastRange.Column];
                }
                ParsePartTemplate(realFirstRange, realLastRange);

                realFirstRange = realLastRange = null;
            }
            catch (Exception ex)
            {
                string message = string.Format("The parsing of template definition '{0}' in sheet '{1}' failed: {2}", Name, worksheet.Name.EmptyIfNull(), ex.Message);
                throw new EasyToolkitException(message, ex);
            }
            finally
            {
                if (worksheet != null)
                    Marshal.ReleaseComObject(worksheet);
            }
        }

        private void RetrieveHeaderAndFooter(Range firstRange, Range lastRange, out int headerSize, out int footerSize)
        {
            headerSize = footerSize = 0;

            Range searchRange = null;
            int width = lastRange.Column - firstRange.Column - 1;
            int height = lastRange.Row - firstRange.Row;

            // Retrieve header and footer
            if (this.Orientation == Orientation.Horizontal)
                searchRange = firstRange.Resize[1, width + 1];
            else
                searchRange = firstRange.Resize[height + 1, 1];

            if (searchRange != null)
            {
                Range endHeader = null;
                Range startHeader = searchRange.Cells.Find(TEMPLATE_START_HEADER_ONELINE, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                if (startHeader == null)
                {
                    startHeader = searchRange.Cells.Find(TEMPLATE_START_HEADER, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                    if (startHeader != null)
                    {
                        endHeader = searchRange.Cells.Find(TEMPLATE_END_HEADER, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                        if (endHeader == null)
                            throw new EasyToolkitException("Cannot find the 'Header' end tag");
                    }
                }

                Range endFooter = null;
                Range startFooter = searchRange.Cells.Find(TEMPLATE_START_FOOTER_ONELINE, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                if (startFooter == null)
                {
                    startFooter = searchRange.Cells.Find(TEMPLATE_START_FOOTER, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                    if (startFooter != null)
                    {
                        endFooter = searchRange.Cells.Find(TEMPLATE_END_FOOTER, Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false);
                        if (endFooter == null)
                            throw new EasyToolkitException("Cannot find the 'Footer' end tag");
                    }
                }

                if (startHeader != null)
                { 
                    if (this.Orientation == Orientation.Horizontal)
                    {
                        if(startHeader.Column != firstRange.Column + 1)
                            throw new EasyToolkitException("The 'Header' tag must be set on the first column after the beginning of the template declaration'");
                        if (endHeader != null && endHeader.Column < startHeader.Column)
                            throw new EasyToolkitException("The '<Header>' tag must be set before '<Header/>' one");
                        headerSize = endHeader == null ? 1 : endHeader.Column - startHeader.Column + 1;
                    }
                    else
                    {
                        if (startHeader.Row != firstRange.Row + 1)
                            throw new EasyToolkitException("The 'Header' tag must be set on the first dataRow after the beginning of the template declaration'");
                        if (endHeader != null && endHeader.Row < startHeader.Row)
                            throw new EasyToolkitException("The '<Header>' tag must be set before '</Header>' one");
                        headerSize = endHeader == null ? 1 : endHeader.Row - startHeader.Row + 1;
                    }
                }

                if (startFooter != null)
                {
                    if (this.Orientation == Orientation.Horizontal)
                    {
                        if (endFooter == null && startFooter.Column != firstRange.Column + Width ||
                            endFooter != null && endFooter.Column != firstRange.Column + Width)
                            throw new EasyToolkitException("The end of the 'Footer' tag must be set on last column of the template cells declaration'");
                        if (endFooter != null && startFooter.Column > endFooter.Column)
                            throw new EasyToolkitException("The '<Footer>' tag must be set before '</Footer>' one");
                        footerSize = endFooter == null ? Width : startFooter.Column - firstRange.Column + 1;
                    }
                    else
                    {
                        if (endFooter == null && startFooter.Row != firstRange.Row + Height ||
                            endFooter != null && endFooter.Row != firstRange.Row + Height)
                            throw new EasyToolkitException("The end of the 'Footer' must be set on last dataRow of the template cells declaration'");
                        if (endFooter != null && startFooter.Row > endFooter.Row)
                            throw new EasyToolkitException("The '<Footer>' tag must be set before '</Footer>' one");
                        footerSize = endFooter == null ? Height : startFooter.Row - firstRange.Row + 1;
                    }
                }
                startHeader = startFooter = endHeader = endFooter = null;

            }
            searchRange = null;
        }

        private void RetrieveContextualMenuMethod(Range firstRange)
        {
            string contextMenu = (TemplateOption as ExcelTemplateOption).ContextMenu;
            if (! string.IsNullOrEmpty(contextMenu) && this.MainBindingDefinition != null)
            {
                Type mainBindingDefinitionType = this.MainBindingDefinition.BindingTypeIsGeneric ? this.MainBindingDefinition.BindingGenericType : this.MainBindingDefinition.BindingType;
                ContextualMenu = ContextualMenuFactory.CreateInstance(firstRange, contextMenu, mainBindingDefinitionType);
            }
        }

        private void RetrieveSelectionChangeMethod()
        {
            string selectionChanged = (TemplateOption as ExcelTemplateOption).SelectionChanged.EmptyIfNull();
            selectionChanged = selectionChanged.Trim();

            if(! string.IsNullOrEmpty(selectionChanged))
            {
                try
                {
                    Type type = this.MainBindingDefinition.BindingTypeIsGeneric ? this.MainBindingDefinition.BindingGenericType : this.MainBindingDefinition.BindingType;
                    SelectionChanged = ConstextualMethodRetriever.RetrieveContextualMethodInfo(type, selectionChanged);

                    ParameterInfo[] parameters = SelectionChanged.GetParameters();
                    Type returnType = SelectionChanged.ReturnType;
                    if (returnType != typeof(bool) || parameters == null || parameters.Count() > 3)
                        throw new EasyToolkitException(string.Format("Method definition must be 'bool MethodName([Range <targeted range>,] [object <catching object>,] object <targeted object>)' instead of '{0}'", selectionChanged));

                    if (parameters.Count() == 3 && !parameters[0].ParameterType.FullName.Equals("Microsoft.Office.Interop.Excel.Range"))
                        throw new EasyToolkitException(string.Format("Method definition must be 'bool MethodName([Range <targeted range>,] [object <catching object>,] object <targeted object>)' instead of '{0}'", selectionChanged));
                }
                catch (Exception ex)
                {
                    throw new EasyToolkitException("Retrieve 'ChangeSelection' method information failed", ex);
                }
            }
        }
        #endregion

        #region public static methods
        public static ExcelTemplateDefinition CreateInstance(string templateName, Range templateDeclarationFirstCell)
        {
            if (string.IsNullOrEmpty(templateName))
                throw new EasyToolkitException("Template rangeName cannot be null or empty.");

            ExcelTemplateDefinition definition = null;
            Worksheet worksheet = null;
            try
            {
                if (templateDeclarationFirstCell == null)
                    throw new EasyToolkitException("Template range cannot be null.");

                //Get template option
                ExcelTemplateOption templateOption = (templateDeclarationFirstCell.Value2 as string).Deserialize<ExcelTemplateOption>();

                // Get the templaet end.
                worksheet = templateDeclarationFirstCell.Worksheet;
                Range templateDeclarationLastRange = worksheet.Cells.Find(string.Format(ExcelTemplateDefinition.TEMPLATE_END_FORMAT, templateName), Type.Missing, XlFindLookIn.xlValues, XlLookAt.xlPart, XlSearchOrder.xlByRows, XlSearchDirection.xlNext, false); 
                if (templateDeclarationLastRange == null)
                    throw new EasyToolkitException(string.Format("Cannot find the end of template '{0}' in sheet '{1}'", templateName.EmptyIfNull(), worksheet.Name.EmptyIfNull()));

                definition = new ExcelTemplateDefinition(templateName, templateDeclarationFirstCell, templateDeclarationLastRange, templateOption);

                definition.BindingType = BindingType.CreateInstance(definition);             
                return definition;
            }
            catch (Exception ex)
            {
                string message = string.Format("Cannot create the template definition '{0}'. {1}", templateName.EmptyIfNull(), ex.Message);
                throw new EasyToolkitException(message, ex);
            }
            finally
            {
                worksheet = null;
            }
        }
        #endregion
    }
}
